package middleware

import (
	_ "embed"
	"net/http"
	"paper-pro/pkg/ctxdata"
	"paper-pro/pkg/result"
	"paper-pro/pkg/xerr"
	"strconv"
	"time"

	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/core/stores/redis"
)

var ErrrConcurrencyLimitExceeded = xerr.NewErrMsg("用户并发请求超过系统限制")

var (
	//go:embed concurrencycheck.lua
	concurrencycheckLuaScript string
	prefixUploadConcurrentKey = "paperfile:upload:concurrent:"
)

type ConcurrencyLimitMiddleware struct {
	redisClient   *redis.Redis
	script        *redis.Script
	maxConcurrent int
	expire        time.Duration
}

func NewConcurrencyLimitMiddleware(redisClient *redis.Redis, maxConcurrent int) *ConcurrencyLimitMiddleware {
	return &ConcurrencyLimitMiddleware{
		redisClient:   redisClient,
		script:        redis.NewScript(concurrencycheckLuaScript),
		maxConcurrent: maxConcurrent,
		expire:        30 * time.Minute,
	}
}

func (m *ConcurrencyLimitMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		userId, err := ctxdata.GetUidFromCtx(ctx)
		if err != nil {
			result.HttpResult(r, w, nil, err)
			return
		}

		key := prefixUploadConcurrentKey + strconv.FormatInt(userId, 10)
		resp, err := m.redisClient.ScriptRunCtx(ctx, m.script, []string{key},
			[]string{
				strconv.Itoa(m.maxConcurrent),
				strconv.Itoa(int(m.expire.Seconds())),
			})
		// Passthrough to next handler if need
		if err != nil { // 记录错误，但是不阻塞执行
			logx.WithContext(ctx).Errorf("ConcurrencyLimitMiddleware: concurrencycheck.lua script run error: %v", err)
			next(w, r)
			return
		}
		if res, ok := resp.(int64); !ok { // 记录错误，但是不阻塞执行
			logx.WithContext(ctx).Errorf("ConcurrencyLimitMiddleware: concurrencycheck.lua resp convert to int64 failed")
			next(w, r)
		} else if res == 0 {
			result.HttpResult(r, w, nil, ErrrConcurrencyLimitExceeded)
		} else {
			defer m.redisClient.DecrCtx(r.Context(), key) // 完成后减计数
			next(w, r)
		}
	}
}
